/*
 * mcu_event.c
 *
 * mcu event routines
 *
 * Copyright (C) 2020 Long-Horn
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <termios.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/select.h> 
#include <sys/time.h> 
#include <fcntl.h>
#include <limits.h>
#include <string.h> 
#include <errno.h>
#include <pthread.h>

#include "mcu_event.h"

static int  mcu_event_proxy_start(void *mcu_event_proxy)
{
    mcu_event_proxy_s * pproxy = mcu_event_proxy;
    
    pthread_mutex_lock(&pproxy->mutex);
    pproxy->running = 1;
    pthread_cond_signal(&pproxy->startable);
    pthread_mutex_unlock(&pproxy->mutex);
   
    return 0;
}

static int mcu_event_proxy_stop(void *mcu_event_proxy)
{
    mcu_event_proxy_s * pproxy = mcu_event_proxy;
    
    pproxy->running = 0;
    
    return 0;
}

static int send_data_to_io(void *mcu_event_proxy, void *buf, int count)
{
    int written_len;
    
    mcu_event_proxy_s * pproxy = mcu_event_proxy;
    if(pproxy->io_type) {
        written_len = pproxy->serial_io->write(pproxy->serial_io, buf, count);
    } else {
        written_len = pproxy->fifo_io->write(pproxy->fifo_io, buf, count);
    }
    
    return written_len;
}

static int mcu_event_proxy_send_event(void *mcu_event_proxy, event_s *event)
{
    int written_len;
    
    mcu_event_proxy_s * pproxy = mcu_event_proxy;
    written_len = send_data_to_io(pproxy, event->payload, event->payload_len);
    
    return written_len;
}

static int mcu_event_proxy_send_event_with_response(void *mcu_event_proxy, event_s *event)
{
    mcu_event_proxy_s * pproxy = mcu_event_proxy;
    
    return 0;
}

static int get_data_from_io(void *mcu_event_proxy, void *buf, int count)
{
    int read_len;
    mcu_event_proxy_s * pproxy = mcu_event_proxy;
    
    if(pproxy->io_type) {
        read_len = pproxy->serial_io->read(pproxy->serial_io, buf, count);
    } else {
        read_len = pproxy->fifo_io->read(pproxy->fifo_io, buf, count);
    }
    
    return read_len;
}

static int mcu_event_proxy_write_respone(void *mcu_event_proxy, void *response, int count)
{
    int ret;
    
    mcu_event_proxy_s * pproxy = mcu_event_proxy;
    ret = send_data_to_io(pproxy, response, count);
    
    return ret;
}

/* Main routine for the receive thread */
void * mcu_event_proxy_receive_handle(void *data)
{
    event_s event;

    char io_buf[IO_BUF_LEN];
    char response[IO_BUF_LEN];
    int fd_max, rio_fd;
    int io_count, event_count;
    int res;
    fd_set rfds;
    mcu_event_proxy_s *pproxy = (mcu_event_proxy_s *)data;
    
    struct timeval tv = {pproxy->timeout/1000, (pproxy->timeout - 1000 * pproxy->timeout / 1000) *  1000};
    struct timeval *ptv = &tv;
    
    printf("%s: enter!\n", __func__);
    
    io_count = 0;
    event_count = 0;
    if (pproxy->io_type) {
        rio_fd = pproxy->serial_io->fd;
    } else {
        rio_fd = pproxy->fifo_io->rfd;
    }

    fd_max = rio_fd;

    event.priority = 0;
    event.from = 1;
    event.to = 0;
    event.payload_len = PAYLOAD_LEN_MAX;

    pthread_mutex_lock(&pproxy->mutex);
    while ( 0 == pproxy->running) {
        pthread_cond_wait(&pproxy->startable, &pproxy->mutex);
    }
    pthread_mutex_unlock(&pproxy->mutex);

    while (pproxy->running) {
        FD_ZERO(&rfds);
        
        /* Always ask for read notification to check for EOF */
        FD_SET(rio_fd, &rfds);
        
        /* Reset timeout values */
        tv.tv_sec = pproxy->timeout / 1000;
        tv.tv_usec = (pproxy->timeout - 1000 * (pproxy->timeout / 1000)) *  1000;
        
        /* Wait for read/write events */
        res = select(fd_max + 1, &rfds, NULL, NULL, ptv);
        if (res == -1) {
        fprintf(stderr, "%s: select() error\n", __func__);
        break;
        }
        
        /* Use the select result for timeout detection */
        if (res == 0) {
        fprintf(stderr, "%s: - timed out\n", __func__);
        }
            
        if (FD_ISSET(rio_fd, &rfds)) {
            io_count = get_data_from_io(pproxy, io_buf, sizeof(io_buf));
            if (io_count <= 0) {
                fprintf(stderr, "get_data_from_io - EOF from io_type=%d \n", pproxy->io_type);
            }
            else {
                event_count = event_wrap_data(&event, io_buf, io_count);
                /* Write to event queue */
                if (event_count > 0) {
                    res = pproxy->event_queue->enqueue(pproxy->event_queue, &event);
                    if (res < 0) {
                        printf("%s : - write to event queue failed\n", __func__);
                    }
                    
                    res = event_make_response(&event, response, 128);
                    if (res > 0) {
                        res = mcu_event_proxy_write_respone(pproxy, response, res);
                    }
                }
            }
        }
    }

    fprintf(stderr, "%s exiting\n", __func__);

    pthread_exit((void *)0);

    return (void *)0;
}

int create_mcu_event_proxy(mcu_event_proxy_s **mcu_event_proxy, event_queue_s *event_queue, int io_type, int timeout)
{
    int res = -1;
    
    mcu_event_proxy_s *pproxy = NULL;
    pproxy = (mcu_event_proxy_s *)malloc(sizeof(mcu_event_proxy_s));
    if(NULL == pproxy) {
        printf("### %s: malloc mcu_event_proxy_s failed!\n", __func__);
        return res;
    }
    
    pproxy->running = 0;
    pproxy->event_queue = event_queue;
    pproxy->io_type = io_type;
    pproxy->timeout = timeout;

    pproxy->start = mcu_event_proxy_start;
    pproxy->stop = mcu_event_proxy_stop;
    pproxy->send_event = mcu_event_proxy_send_event;
    pproxy->send_event_with_response = mcu_event_proxy_send_event_with_response;
        
    pthread_mutex_init(&pproxy->mutex, NULL);
    pthread_cond_init(&pproxy->startable, NULL);

    *mcu_event_proxy = pproxy;

    switch(io_type) {
        case 0:
            res = create_fifo_io(&pproxy->fifo_io, "/tmp/fifo_io_wr", "/tmp/fifo_io_rd.");
            if(res >= 0) {
                res = pproxy->fifo_io->open(pproxy->fifo_io);
            }
        break;
        
        case 1:
        default:
            res = create_serial_io(&pproxy->serial_io, "/dev/ttyS0", 115200);
            if(res >= 0) {
                 res = pproxy->serial_io->open(pproxy->serial_io);
            }
        break;
    }
    
    if (res >= 0) {
        pthread_attr_t attr;
        pthread_t thread;

        pthread_attr_init(&attr);
        pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
        res = pthread_create(&thread, &attr, mcu_event_proxy_receive_handle, (void *)pproxy);
        pthread_attr_destroy(&attr);
    }
    
    if (res < 0) {
        if (io_type) {
            pproxy->serial_io->close(pproxy->serial_io);
        } else {
            pproxy->fifo_io->close(pproxy->fifo_io);        
        }

        destroy_mcu_event_proxy(pproxy);
        printf("%s: failed!\n", __func__);
    } else {
        printf("%s: OK!\n", __func__);
    }

    return res;
}

void destroy_mcu_event_proxy(mcu_event_proxy_s *mcu_event_proxy)
{
    if (mcu_event_proxy->io_type) {
        destroy_serial_io(mcu_event_proxy->serial_io);
    } else {
        destroy_fifo_io(mcu_event_proxy->fifo_io);
    }

    free(mcu_event_proxy);
}
